package com.durian.base.rxhttp.await

import com.durian.base.rxhttp.CallCancelFactory
import com.durian.base.rxhttp.RxHttp
import com.durian.base.rxhttp.cache.CacheMode
import com.durian.base.rxhttp.exception.CacheReadFailedException
import com.durian.base.rxhttp.param.IParam
import com.durian.base.rxhttp.parse.Parser
import com.durian.base.rxhttp.download.*
import com.durian.base.rxhttp.logging.LoggingInterceptor
import com.durian.base.rxhttp.param.RxBaseParam
import com.durian.base.rxhttp.parse.SuspendParser
import com.durian.base.rxhttp.utils.OkHttpCompat
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withContext
import okhttp3.*
import java.io.IOException
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException

/**
 * 说明：AwaitImpl
 * <p/>
 * 作者：Fanly
 * <p/>
 * 类型：Class
 * <p/>
 * 时间：2020/04/25 18:19
 * <p/>
 * 版本：version 1.0
 */
class AwaitImpl<T>(
        val param: IParam<T>,
        val parser: Parser<T>,
        private val client : OkHttpClient
) : IAwait<T> {

    val cache by lazy { RxHttp.getCacheManager()}
    val cacheStrategy by lazy { param.buildCacheStrategy() }
    val cancelTag by lazy { if (param is RxBaseParam){ param.cancelTag }else "" }

    override suspend fun await(): T {

        return if (parser is SuspendParser){
            if (RxHttp.isDebug()){
                param.addHeader(LoggingInterceptor.RXHTTP_IGNORE_LOG,"1")
            }
            val request = param.buildRequest()
            parser.cancelTag = cancelTag
            parser.onSuspendParse(RxHttp.newCall(client,request).suspendAwait(this))
        }else{
            val request = param.buildRequest()
            val cacheT = beforeReadCache(request)
            if (cacheT != null) return cacheT
            RxHttp.newCall(client,request).await(this)
        }
    }

    private suspend fun beforeReadCache(request: Request) : T?{
        return if (cacheModeIs(CacheMode.ONLY_CACHE)){
            withContext(Dispatchers.IO){
                //读取缓存
                val cacheResponse = getCacheResponse(request, cacheStrategy.cacheValidTimeMillis)
                if (cacheResponse == null) {
                    if (cacheModeIs(CacheMode.ONLY_CACHE)) throw CacheReadFailedException("Cache read failed")
                    return@withContext null
                }
                parser.onParse(cacheResponse)
            }
        }else{
            null
        }
    }

    fun cacheModeIs(vararg cacheModes : CacheMode) : Boolean{
        cacheModes.forEach {
            if (it == cacheStrategy.cacheMode){
                return true
            }
        }
        return false
    }

    fun getCacheResponse(request: Request,cacheValidTimeMillis:Int) : Response?{
        val cacheResponse = cache?.get(request, cacheStrategy.cacheKey)
        if (cacheResponse != null) {
            val receivedTime = OkHttpCompat.receivedResponseAtMillis(cacheResponse)
            //缓存过期，返回null
            return if (cacheValidTimeMillis != -1 && System.currentTimeMillis() - receivedTime > cacheValidTimeMillis) null else cacheResponse
        }
        return null
    }

}

internal suspend fun <T> Call.suspendAwait(impl : AwaitImpl<T>):Response{
    return suspendCancellableCoroutine { continuation ->
        continuation.invokeOnCancellation {
            if (impl.cancelTag.isNotEmpty()){
                CallCancelFactory.removeCall(impl.cancelTag)
            }else{
                if (!isCanceled()){
                    cancel()
                }
            }
        }
        if (impl.cancelTag.isNotEmpty()){
            CallCancelFactory.addCall(impl.cancelTag,this)
        }
        enqueue(object : Callback {
            override fun onResponse(call: Call, response: Response) {
                continuation.resume(response)
            }
            override fun onFailure(call: Call, e: IOException) {
                continuation.resumeWithException(e)
                if (impl.cancelTag.isNotEmpty()){
                    CallCancelFactory.removeCall(impl.cancelTag)
                }
            }
        })
    }
}

internal suspend fun <T> Call.await(impl : AwaitImpl<T>):T{
    return suspendCancellableCoroutine {continuation->
        continuation.invokeOnCancellation {
            if (impl.cancelTag.isNotEmpty()){
                CallCancelFactory.removeCall(impl.cancelTag)
            }else{
                if (!isCanceled()){
                    cancel()
                }
            }
        }
        if (impl.cancelTag.isNotEmpty()){
            CallCancelFactory.addCall(impl.cancelTag,this)
        }
        enqueue(object : Callback {
            override fun onFailure(call: Call, e: IOException) {
                var networkResponse : Response? = null
                if (impl.cacheModeIs(CacheMode.REQUEST_NETWORK_FAILED_READ_CACHE)){
                    networkResponse = impl.getCacheResponse(call.request(),impl.cacheStrategy.cacheValidTimeMillis)
                }
                if (networkResponse == null){
                    continuation.resumeWithException(e)
                }else{
                    onSuccess(networkResponse)
                }
                if (impl.cancelTag.isNotEmpty()){
                    CallCancelFactory.removeCall(impl.cancelTag)
                }
            }

            override fun onResponse(call: Call, response: Response) {
                if (impl.cache != null){
                    val networkResponse = if (impl.cacheModeIs(CacheMode.REQUEST_NETWORK_FAILED_READ_CACHE)){
                        impl.cache?.put(response,impl.cacheStrategy.cacheKey)
                    }else{
                        response
                    }
                    networkResponse?.let { onSuccess(it) }
                }else{
                    onSuccess(response)
                }
                if (impl.cancelTag.isNotEmpty()){
                    CallCancelFactory.removeCall(impl.cancelTag)
                }
            }

            private fun onSuccess(response: Response) {
                try {
                    continuation.resume(impl.parser.onParse(response))
                } catch (e: Throwable) {
                    continuation.resumeWithException(e)
                }
            }
        })
    }
}